#include "ProcessManager.h"
#include "../XXqtDefine.h"
#include <mutex>
#include <functional>
#include <QVariant>

#include "XXlogger.h"

ProcessManager* ProcessManager::_instance = nullptr;

ProcessManager::ProcessManager(QObject *parent)
    :QObject (parent)
{
    qRegisterMetaType<QProcess::ProcessState>("QProcess::ProcessState");
    connect(this,&ProcessManager::sigDeleteLater,this,&ProcessManager::onDeleteLater,Qt::QueuedConnection);
}

void ProcessManager::killApp(const QString &appName){
    QString cmd = "taskkill /im " + appName + " /f";
    QProcess process;
    process.start(cmd);
    process.waitForFinished(-1);
}
void ProcessManager::killApp(const QStringList &appNames){
    foreach (auto name, appNames) {
        killApp(name);
    }
}
bool ProcessManager::existsApp(const QString &appName){
    QProcess process;
    process.start(QString("tasklist /FI \"IMAGENAME eq %1\"").arg(appName));
    process.waitForFinished();
    QString string = QString::fromLocal8Bit(process.readAllStandardOutput());
    if(appName.contains('*')){
        return string.contains(QString(appName).replace('*',""));
    }
    else{
        return string.contains(appName);
    }
}

ProcessManager* ProcessManager::instance(){
    XX_ONCE([&]{
        _instance = new ProcessManager;
    });
    return _instance;
}
bool ProcessManager::exists(const QString &name){
    return _nameToProcess.contains(name);
}
bool ProcessManager::existsPrefix(const QString &prefix){
    if(_nameToProcess.isEmpty()){
        return false;
    }
    for (auto iter=_nameToProcess.begin(); iter!=_nameToProcess.end(); iter++) {
        if(iter.key().startsWith(prefix)){
            return true;
        }
    }
    return false;
}
QProcess::ProcessState ProcessManager::state(const QString &name){
    auto process = getProcess(name);
    if(nullptr == process){
        xxErrStr(QString("could not find process with name. name:%1").arg(name));
        return QProcess::ProcessState::NotRunning;
    }
    return process->process.state();
}
void ProcessManager::close(const QString &name){
    if(!exists(name)){
        xxErrStr("not exists process for name. name:"+name);
        return;
    }
    auto process = getProcess(name); // remove will be work at state changed
    process->process.close();
}
void ProcessManager::closePrefix(const QString &prefix){
    auto processs = getProcessWithPrefix(prefix);   // remove will be work at state changed
    if (processs.isEmpty()) {
        xxErrStr("not exists process for prefix. prefix:"+prefix);
        return;
    }
    foreach(auto process, processs){
        process->process.close();
    }
}
void ProcessManager::async(const QString &name, const QString &appPath, const QStringList &param, const QString &workDir){
    if(exists(name)){
        xxErrStr("already exists process for name. name:"+name);
        return;
    }

    QString cmd = appPath;
    for (int index=0; index<param.count(); index++) {
        cmd += " " + param[index];
    }
    xxInfoStr(QString("process. name:%1 cmd:%2").arg(name).arg(cmd));

    auto process = QSharedPointer<ProcessInfo>(new ProcessInfo);
    if(!workDir.isEmpty()){
        process->process.setWorkingDirectory(workDir);
    }
    addProcess(name,process);
    process->process.start(cmd);
}
void ProcessManager::async(const QString &name, const QString &appPath, const QString &workDir){
    if(exists(name)){
        xxErrStr("already exists process for name. name:"+name);
        return;
    }
    auto process = QSharedPointer<ProcessInfo>(new ProcessInfo);
    if(!workDir.isEmpty()){
        process->process.setWorkingDirectory(workDir);
    }
    addProcess(name,process);
    process->process.start(appPath);
}
void ProcessManager::async(const QString &name,
                           const QString &appPath,
                           const QStringList &param,
                           const QString &workDir,
                           std::function<void(QByteArray data)> readyReadBlock,
                           std::function<void(QProcess::ProcessState newState)> stateChangedBlock,
                           std::function<void(QProcess::ProcessError error)> errorOccurredBlock){
    if(exists(name)){
        xxErrStr("already exists process for name. name:"+name);
        return;
    }
    auto process = QSharedPointer<ProcessInfo>(new ProcessInfo);
    if(!workDir.isEmpty()){
        process->process.setWorkingDirectory(workDir);
    }
    process->readyReadStandardOutputBlock = readyReadBlock;
    process->stateChangedBlock = stateChangedBlock;
    process->errorOccurredBlock = errorOccurredBlock;

    QString cmd = appPath;
    for (int index=0; index<param.count(); index++) {
        cmd += " " + param[index];
    }
    xxInfoStr(QString("process. name:%1 cmd:%2").arg(name).arg(cmd));

    addProcess(name,process,nullptr!=stateChangedBlock,nullptr!=errorOccurredBlock,nullptr!=readyReadBlock);
    process->process.start(appPath);
}

void ProcessManager::write(const QString &name, const QByteArray &data){
    auto processInfo = getProcess(name);
    if(processInfo == nullptr){
        xxErrStr("not exists process for name. name:"+name);
        return;
    }
    processInfo->process.write(data);
}

QSharedPointer<ProcessInfo> ProcessManager::getProcess(const QString &name){
    return _nameToProcess.value(name,nullptr);
}
QList<QSharedPointer<ProcessInfo>> ProcessManager::getProcessWithPrefix(const QString &prefix){
    QList<QSharedPointer<ProcessInfo>> list;
    if(_nameToProcess.isEmpty()){
        return list;
    }
    for (auto iter=_nameToProcess.begin(); iter!=_nameToProcess.end();iter++) {
        if(iter.key().startsWith(prefix)){
            list << *iter;
        }
    }
    return list;
}
QSharedPointer<ProcessInfo> ProcessManager::takeProcess(const QString &name){
    QMutexLocker locker(&_mutex);
    return _nameToProcess.contains(name) ? _nameToProcess.take(name) : nullptr;
}
QList<QSharedPointer<ProcessInfo>> ProcessManager::takeProcessWithPrefix(const QString &prefix){
    QMutexLocker locker(&_mutex);
    QList<QSharedPointer<ProcessInfo>> list;
    if(_nameToProcess.isEmpty()){
        return list;
    }
    for (auto iter=_nameToProcess.begin(); iter!=_nameToProcess.end();) {
        if(iter.key().startsWith(prefix)){
            list << *iter;
            iter = _nameToProcess.erase(iter);
        }
        else{
            iter++;
        }
    }
    return list;
}

QString ProcessManager::getName(QProcess *process){
    QMutexLocker locker(&_mutex);
    for (auto iter=_nameToProcess.begin(); iter!=_nameToProcess.end(); iter++) {
        if(&(iter.value()->process) == process){
            return iter.key();
        }
    }
    return "";
}
void ProcessManager::addProcess(const QString &name, QSharedPointer<ProcessInfo> process, bool stateConnect, bool errorConnect, bool readyReadConnect){
    QMutexLocker locker(&_mutex);
    _nameToProcess[name] = process;
    if(stateConnect) connect(&(process->process),&QProcess::stateChanged,this,&ProcessManager::process_onStateChanged);
    if(errorConnect) connect(&(process->process),&QProcess::errorOccurred,this,&ProcessManager::process_onErrorOccurred);
    if(readyReadConnect) connect(&(process->process),&QProcess::readyReadStandardOutput,this,&ProcessManager::process_onReadyReadStandardOutput);
}
void ProcessManager::removeProcess(const QString &name){
    QMutexLocker locker(&_mutex);
    auto process = _nameToProcess.take(name);
    if(process != nullptr){
        disconnect(&(process->process),&QProcess::stateChanged,this,&ProcessManager::process_onStateChanged);
        disconnect(&(process->process),&QProcess::errorOccurred,this,&ProcessManager::process_onErrorOccurred);
        disconnect(&(process->process),&QProcess::readyReadStandardOutput,this,&ProcessManager::process_onReadyReadStandardOutput);
    }
    emit sigDeleteLater(process);
}
void ProcessManager::removeProcessWithPrefix(const QString &prefix){
    QMutexLocker locker(&_mutex);
    if(_nameToProcess.isEmpty()){
        return;
    }

    for (auto iter=_nameToProcess.begin(); iter!=_nameToProcess.end();) {
        if(iter.key().startsWith(prefix)){
            emit sigDeleteLater(*iter);
            iter = _nameToProcess.erase(iter);
        }
        else{
            iter++;
        }
    }
}

void ProcessManager::process_onStateChanged(QProcess::ProcessState newState){
    QProcess *process = qobject_cast<QProcess*>(sender());
    QString name = getName(process);
    if(name.isEmpty()){
        xxInfo("can not find name with process. process:%p",process);
        return;
    }

    auto processInfo = QProcess::ProcessState::NotRunning == newState ? takeProcess(name) : getProcess(name);
    if(nullptr == processInfo){
        xxInfoStr(QString("can not find process info. name:%1 process:%2").arg(name).arg((int)process));
        return;
    }

    xxInfoStr(QString("name:%1 process:%2 state:%3").arg(name).arg((int)process).arg(newState));
    if(nullptr != processInfo->stateChangedBlock){
        processInfo->stateChangedBlock(newState);
    }
    emit sigStateChanged(name,newState);

    if(QProcess::ProcessState::NotRunning == newState){
        emit sigDeleteLater(processInfo);
    }
}
void ProcessManager::process_onErrorOccurred(QProcess::ProcessError error){
    QProcess *process = qobject_cast<QProcess*>(sender());
    QString name = getName(process);
    if(name.isEmpty()){
        xxErr("can not find name with process. process:%p",process);
        return;
    }
    auto processInfo = getProcess(name);
    if(nullptr == processInfo){
        xxErrStr(QString("can not find process info. name:%1 process:%2").arg(name).arg((int)process));
        return;
    }

    xxInfoStr(QString("name:%1 process:%2 error:%3").arg(name).arg((int)process).arg(error));
    if(nullptr != processInfo->errorOccurredBlock){
        processInfo->errorOccurredBlock(error);
    }
    emit sigErrorOccurred(name,error);
}
void ProcessManager::process_onReadyReadStandardOutput(){
    QProcess *process = qobject_cast<QProcess*>(sender());
    QString name = getName(process);
    if(name.isEmpty()){
        xxErr("can not find name with process. process:%p",process);
        return;
    }

    auto data = process->readAll();
    auto processInfo = getProcess(name);
    if(nullptr == processInfo){
        xxErrStr(QString("can not find process info. name:%1 process:%2").arg(name).arg((int)process));
        return;
    }

    xxInfoStr(QString("name:%1 %2").arg(name).arg(QString::fromLocal8Bit(process->readAllStandardOutput())));
    if(nullptr != processInfo->readyReadStandardOutputBlock){
        processInfo->readyReadStandardOutputBlock(data);
    }
    emit sigReadReady(name,data);
}
